﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Library.Utils
{
    public static class ByteExtensions
    {
        private const int BIT_SIZE_BYTE = 8;

        private const int BIT_SIZE_SHORT = 8;
        /// <summary>
        /// Gets the size of the input value in bits
        /// </summary>
        /// <param name="pInput">The input value</param>
        /// <returns></returns>
        public static int SizeOf(this byte pInput)
        {
            int iRetval = 0;
            if (pInput == 0)
            {
                iRetval = 0;
            }
            else if (pInput == 1)
            {
                iRetval = 1;
            }
            else if (pInput < 0)
            {
                iRetval = BIT_SIZE_BYTE;
            }
            else
            {
                int lTemp = 0;
                for (int i = BIT_SIZE_BYTE - 1; i > 1; i--)
                {
                    lTemp = 1 << i - 1;
                    if ((pInput & lTemp) == lTemp)
                    {
                        iRetval = i;
                        break;
                    }
                }
            }
            return iRetval;
        }


        /// <summary>
        /// Gets the bits from a number as a number.
        /// </summary>
        /// <param name="pInput">The input value.</param>
        /// <param name="pStart">The start position.</param>
        /// <returns></returns>
        public static byte GetBits(byte pInput, int pStartIndex)
        {
            return GetBits(pInput, pStartIndex, BIT_SIZE_BYTE, false);
        }


        /// <summary>
        /// Gets the bits.
        /// </summary>
        /// <param name="pInput">The p input.</param>
        /// <param name="pStartIndex">Start index of the p.</param>
        /// <param name="pShift">if set to <c>true</c> [p shift].</param>
        /// <returns></returns>
        public static byte GetBits(byte pInput, int pStartIndex, bool pShift)
        {
            return GetBits(pInput, pStartIndex, BIT_SIZE_BYTE, pShift);
        }


        /// <summary>
        /// Gets the bits.
        /// </summary>
        /// <param name="pInput">The p input.</param>
        /// <param name="pStartIndex">Start index of the p.</param>
        /// <param name="pLength">Length of the p.</param>
        /// <returns></returns>
        public static byte GetBits(byte pInput, int pStartIndex, int pLength)
        {
            return GetBits(pInput, pStartIndex, pLength, false);
        }


        /// <summary>
        /// Gets a number in the specified range of bits
        /// </summary>
        /// <param name="pStart"></param>
        /// <param name="pEnd"></param>
        /// <returns></returns>
        public static byte GetBits(byte pInput, int pStartIndex, int pLength, bool pShift)
        {
            int lRetval = 0, lSize = 0, lTemp = 0;
            int lPosition = 1;
            if (pInput < 2 && pInput > 0)
            {
                return pInput; //Should be either a 0 or 1
            }
            lSize = SizeOf(pInput);


            if (pStartIndex < 1 || pStartIndex > BIT_SIZE_SHORT)
            {
                throw new ArgumentException("Start bit is out of range.", "pStartIndex");
            }
            if (pLength < 0 || pLength + pStartIndex > BIT_SIZE_BYTE + 1)
            {
                throw new ArgumentException("End bit is out of range.", "pLength");
            }
            for (int i = pStartIndex; (i < pLength + pStartIndex) && (lPosition <= lSize); i++)
            {
                lTemp = 1 << i - 1;
                if ((pInput & lTemp) == lTemp)
                {
                    lRetval |= (1 << (lPosition - 1));
                }
                lPosition++;
            }
            if (pShift && lPosition < lSize)
            {
                lRetval <<= lSize - lPosition;
            }
            return (byte)lRetval;
        }


        /// <summary>
        /// Sets the bits.
        /// </summary>
        /// <param name="pDest">The p dest.</param>
        /// <param name="pSource">The p source.</param>
        /// <param name="pSourceIndex">Index of the p source.</param>
        /// <returns></returns>
        public static byte SetBits(byte pDest, byte pSource, int pSourceIndex)
        {
            return SetBits(pDest, pSource, pSourceIndex, 0, BIT_SIZE_BYTE);
        }


        /// <summary>
        /// Sets the bits.
        /// </summary>
        /// <param name="pDest">The p dest.</param>
        /// <param name="pSource">The p source.</param>
        /// <param name="pSourceIndex">Index of the p source.</param>
        /// <param name="pLength">Length of the p.</param>
        /// <returns></returns>
        public static byte SetBits(byte pDest, byte pSource, int pSourceIndex, int pLength)
        {
            return SetBits(pDest, pSource, pSourceIndex, 0, pLength);
        }


        /// <summary>
        /// Sets the bits.
        /// </summary>
        /// <param name="pDest">The dest.</param>
        /// <param name="pSource">The source.</param>
        /// <param name="pSourceIndex">Index of the source.</param>
        /// <param name="pDestIndex">Index of the dest.</param>
        /// <param name="pLength">Length to read.</param>
        /// <returns></returns>
        public static byte SetBits(byte pDest, byte pSource, int pSourceIndex,
            int pDestIndex, int pLength)
        {
            int lSourceSize = 0, lTemp1 = 0;
            if (pSourceIndex < 1 || pSourceIndex > BIT_SIZE_BYTE)
            {
                throw new ArgumentException("Start bit is out of range.", "pSourceIndex");
            }
            if (pDestIndex < 0 || pDestIndex > BIT_SIZE_BYTE)
            {
                throw new ArgumentException("End bit is out of range.", "pDestIndex");
            }
            if (pLength < 0 || pLength + pDestIndex > BIT_SIZE_BYTE)
            {
                throw new ArgumentException("End bit is out of range.", "pLength");
            }
            pSource = GetBits(pSource, pSourceIndex, pLength);
            lSourceSize = SizeOf(pSource);

            int lPosition = 1;
            for (int i = pDestIndex; (i < lSourceSize + pDestIndex); i++)
            {
                lTemp1 = 1 << lPosition - 1;
                if ((pSource & lTemp1) == lTemp1)
                {
                    pDest |= ((byte)(1 << (i - 1)));
                }
                else
                {
                    lTemp1 = 1 << i - 1;
                    if ((pDest & lTemp1) == lTemp1)
                    {
                        pDest ^= ((byte)(1 << (i - 1)));
                    }
                }
                lPosition++;
            }
            return (byte)pDest;
        }


        /// <summary>
        /// Determines whether [is bit set] [the specified p input].
        /// </summary>
        /// <param name="pInput">The p input.</param>
        /// <param name="pPosition">The p position.</param>
        /// <returns>
        /// 	<c>true</c> if [is bit set] [the specified p input]; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsBitSet(this byte pInput, int pPosition)
        {
            return GetBits(pInput, pPosition, 1, false) == 1;
        }

        /// <summary>
        /// Changes the value of the bit at the specified positon
        /// </summary>
        /// <param name="pInput"></param>
        /// <param name="pPosition"></param>
        /// <returns></returns>
        public static byte ChangeBit(this byte pInput, int pPosition)
        {
            if (pPosition > BIT_SIZE_BYTE)
            {
                throw new ArgumentException("Position out of range", "pPosition");
            }
            return pInput ^= (byte)(1 << (pPosition - 1));
        }

        /// <summary>
        /// Sets the value of a bit
        /// </summary>
        /// <param name="pInput">The p input.</param>
        /// <param name="pPosition">The p position.</param>
        /// <param name="pOn">if set to <c>true</c> [p on].</param>
        /// <returns></returns>
        public static byte SetBit(this byte pInput, int pPosition, bool pOn)
        {
            if (pPosition > BIT_SIZE_BYTE)
            {
                throw new ArgumentException("Position out of range", "pPosition");
            }

            if (pOn) pInput = (byte) (pInput | ( 1 << (pPosition - 1)));
            else pInput = (byte) (pInput & ~( 1 << (pPosition -1) ));
            return pInput;
        }
    }
}
